package net.sansi.v3correctserver.service.correct.chroma;

import net.sansi.v3correctserver.entity.IntPoint;
import net.sansi.v3correctserver.entity.color.CIEChroma;
import net.sansi.v3correctserver.entity.image.RecImage;
import net.sansi.v3correctserver.entity.photorec.LedNode;
import net.sansi.v3correctserver.entity.screen.CorrectArea;
import net.sansi.v3correctserver.exception.ServiceException;
import net.sansi.v3correctserver.service.correct.RectCorrectBase;
import net.sansi.v3correctserver.service.photorec.rec.RecRectScreen;
import net.sansi.v3correctserver.util.Matrix;
import net.sansi.v3correctserver.util.MonotoneUtil;
import net.sansi.v3correctserver.util.RGBUtil;
import net.sansi.v3correctserver.util.Transformer;

import java.util.Comparator;
import java.util.List;

/**
 * Created by Fang
 * create time : 2024/11/26
 * IDE : IntelliJ IDEA
 * <p>
 * 色度矫正
 */

public class RectChromaCorrect extends RectCorrectBase {

    // RGB 三张照片的识别结果
    RecRectScreen[] recs = new RecRectScreen[3];
    // 矫正参数
    private final Params params;

    /**
     * 构造函数
     *
     * @param params 矫正参数
     */
    public RectChromaCorrect(Params params) {
        this.params = params;
        CorrectArea area = params.area;
        initBaseParams(
                area.getShowArea().getWidth(),
                area.getShowArea().getHeight(),
                params.span
        );
    }

    /**
     * 仿射转换器，将 srgb 转换为 XYZ 坐标系，用于
     */
    Transformer orgTransformer;

    /**
     * 矫正入口函数
     */
    public void toCorrect() {
        initRecRGB();
        orgTransformer = computeTransformer();
        correctChroma();
    }

    /**
     * 初始化识别对象
     */
    private void initRecRGB() {
        recs[0] = new RecRectScreen(params.photos[0], params.blackPhotos[0], params.fence).toRec();
        recs[1] = new RecRectScreen(params.photos[1], params.blackPhotos[1], params.fence).toRec();
        recs[2] = new RecRectScreen(params.photos[1], params.blackPhotos[1], params.fence).toRec();
        for (int i = 0; i < 3; i++) {
            if (isRecSizeValid(recs[i])) continue;
            throw new ServiceException("行列识别结果与理论不符！[{}]", RGBUtil.colorName(i));
        }
    }

    /**
     * 矫正色度
     */
    private void correctChroma() {
        recs[0].getMatrix().foreach((mx, my, ledR) -> {
            LedNode ledG = recs[1].getMatrix().get(mx, my);
            LedNode ledB = recs[2].getMatrix().get(mx, my);
            if (ledR == null || ledG == null || ledB == null) return;
            double[] brights = new double[]{
                    params.orgChromas[0].getBright(),
                    params.orgChromas[1].getBright(),
                    params.orgChromas[2].getBright()
            };
            CIEChroma[] ledOrgChromas = computeLedCieChroma(ledR, ledG, ledB);
            for (int i = 0; i < 3; i++) {
                double[] devRGB = params.results[i].get(mx, my);
                double devBright = devRGB[0] + devRGB[1] + devRGB[2];
                double[] resXYZ = computeLedDevXYZ(devBright, i, ledOrgChromas);
                double[] resRGB = resXYZ.clone();
                if (i == 0) {
                    resRGB[1] = devBright * resRGB[1] / (brights[1] / brights[0] * devBright);
                    resRGB[2] = devBright * resRGB[2] / (brights[2] / brights[0] * devBright);
                } else if (i == 1) {
                    resRGB[0] = devBright * resRGB[0] / (brights[0] / brights[1] * devBright);
                    resRGB[2] = devBright * resRGB[2] / (brights[2] / brights[1] * devBright);
                } else {
                    resRGB[0] = devBright * resRGB[0] / (brights[0] / brights[2] * devBright);
                    resRGB[1] = devBright * resRGB[1] / (brights[1] / brights[2] * devBright);
                }
                devRGB[0] = resRGB[0];
                devRGB[1] = resRGB[1];
                devRGB[2] = resRGB[2];
            }
        });
    }

    /**
     * 计算 LED 的 CIE 原始色域
     *
     * @param ledR 红色坐标
     * @param ledG 绿色坐标
     * @param ledB 蓝色坐标
     * @return 原始坐标
     */
    private CIEChroma[] computeLedCieChroma(LedNode ledR, LedNode ledG, LedNode ledB) {
        CIEChroma[] chromas = {
                ledR.ledColor.toCIEChroma(),
                ledG.ledColor.toCIEChroma(),
                ledB.ledColor.toCIEChroma()
        };
        Transformer.Point r = orgTransformer.applyTransformation(chromas[0].getX(), chromas[0].getY());
        Transformer.Point g = orgTransformer.applyTransformation(chromas[1].getX(), chromas[1].getY());
        Transformer.Point b = orgTransformer.applyTransformation(chromas[2].getX(), chromas[2].getY());
        return new CIEChroma[]{
                new CIEChroma(r.getX(), r.getY(), chromas[0].getBright()),
                new CIEChroma(g.getX(), g.getY(), chromas[1].getBright()),
                new CIEChroma(b.getX(), b.getY(), chromas[2].getBright())
        };
    }

    /**
     * 计算 srgb -> 原始色域 仿射变换
     *
     * @return 仿射变换器
     */
    private Transformer computeTransformer() {
        int size = Math.min(recs[0].getMatrix().getItemSize() * 100 / (
                params.targetChromas == null ? 200 : 900
        ), 1);
        MonotoneUtil<CIEChroma> r = new MonotoneUtil<>(size, Comparator.comparingDouble(CIEChroma::getX));
        MonotoneUtil<CIEChroma> g = new MonotoneUtil<>(size, Comparator.comparingDouble(CIEChroma::getY));
        MonotoneUtil<CIEChroma> b = new MonotoneUtil<>(size, Comparator.comparingDouble(CIEChroma::getZ));
        recs[0].getMatrix().foreach((mx, my, ledR) -> {
            LedNode ledG = recs[1].getMatrix().get(mx, my);
            LedNode ledB = recs[2].getMatrix().get(mx, my);
            if (ledR == null || ledG == null || ledB == null) return;
            r.add(ledR.ledColor.toCIEChroma());
            g.add(ledG.ledColor.toCIEChroma());
            b.add(ledB.ledColor.toCIEChroma());
        });
        return new Transformer(
                new Transformer.Point[]{
                        new Transformer.Point(r.get().getX(), r.get().getY()),
                        new Transformer.Point(g.get().getX(), g.get().getY()),
                        new Transformer.Point(b.get().getX(), b.get().getY()),
                },
                new Transformer.Point[]{
                        new Transformer.Point(params.orgChromas[0].getX(), params.orgChromas[0].getY()),
                        new Transformer.Point(params.orgChromas[1].getX(), params.orgChromas[1].getY()),
                        new Transformer.Point(params.orgChromas[2].getX(), params.orgChromas[2].getY()),
                }
        );
    }

    /**
     * 计算 LED 设备输出的 XYZ 值
     *
     * @param brightness 亮度
     * @param colorIdx   颜色
     * @param orgChroma  颜色色域
     * @return 设备 xyz
     */
    private double[] computeLedDevXYZ(double brightness, int colorIdx, CIEChroma[] orgChroma) {

        CIEChroma white = orgChroma[colorIdx];

        double rx = orgChroma[0].getX(), ry = orgChroma[0].getY();
        double gx = orgChroma[1].getX(), gy = orgChroma[1].getY();
        double bx = orgChroma[2].getX(), by = orgChroma[2].getY();
        double wx = white.getX(), wy = white.getY();

        double Den = wy * (rx * (gy - by) + gx * (by - ry) + bx * (ry - gy));
        double lr = (brightness * ry * (gx * (by - wy) + bx * (wy - gy) + wx * (gy - by))) / Den;
        double lg = (brightness * gy * (rx * (wy - by) + bx * (ry - wy) + wx * (by - ry))) / Den;
        double lb = (brightness * by * (rx * (gy - wy) + gx * (wy - ry) + wx * (ry - gy))) / Den;

        return new double[]{lr, lg, lb};
    }

    /**
     * 色度矫正参数
     */
    public static class Params {
        // 隔点数量
        public int span;
        // 横向偏移
        public int offsetX;
        // 纵向偏移
        public int offsetY;
        // 矫正区域
        public CorrectArea area;
        // 亮度矫正结果（当前结果）
        public Matrix<double[]>[] results;
        // 亮度矫正后打屏照片 [ r, g, b ]
        public RecImage[] photos;
        // 黑色照片 [ r, g, b ], 可以是 同一张照片
        public RecImage[] blackPhotos = new RecImage[3];
        // 屏幕围栏
        public List<IntPoint> fence;
        // 原始色域
        public CIEChroma[] orgChromas;
        // 目标色域
        public CIEChroma[] targetChromas = new CIEChroma[3];
    }
}
